﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using TCPSockets.ClientEvents.Handlers;
using TCPSockets.Enumirators;
using TCPSockets.Events.Args;
using TCPSockets.Interfaces;
using TCPSockets.ServerEvents.Args;
using TCPSockets.ServerEvents.Handlers;
using DataEventArgs = TCPSockets.ClientEvents.Args.DataEventArgs;
using OnErrorEventHandler = TCPSockets.ServerEvents.Handlers.OnErrorEventHandler;

namespace TCPSockets
{
    /// <summary>
    /// Класс, реализующий серверную программу для TCP соединения
    /// </summary>
    public class CTCPServer : IEnumerable<CTCPClient>, IDisposable, IServer
    {
        /* - События класа ------------------------------------------------------------------------------------------------------------------- */

        #region События

        /// <summary>
        /// Событие, возникающие при запуске сервера  
        /// </summary>
        public event OnStartEventHandler OnStart;

        /// <summary>
        /// Метод вызова события "при запуске сервера"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        protected virtual void InvokeStartEvent()
        {
            if(OnStart != null)
                OnStart.BeginInvoke(this, new EventArgs(), null, null);
        }

        /// <summary>
        /// Событие, возникающие при остановке сервера
        /// </summary>
        public event OnStopEventHandler OnStop;

        /// <summary>
        /// Метод вызова события "при остановке сервера"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        protected virtual void InvokeStopEvent()
        {
            if(OnStop != null)
                OnStop.BeginInvoke(this, new EventArgs(), null, null);
        }

        //Событие "подключение пользователя"
        /// <summary>
        /// Событие, возникающие при подключении нового клиента
        /// </summary>
        public event OnClientConnectedEventHandler OnClientConnected;

        //Метод, подзволяющий упростить вызов события "Подключение клиента"
        /// <summary>
        /// Метод вызова события "при подключении клиента"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        /// <param name="Client">Подключившийся клиент</param>
        protected virtual void InvokeClientConnectedEvent(CTCPClient Client)
        {
            if(OnClientConnected != null)
                OnClientConnected.BeginInvoke(this, new OnClientConnectedEventArgs(Client), null,
                    null);
        }

        //Событие "отключение пользователя"
        /// <summary>
        /// Событие, возникающие при отключении клиента
        /// </summary>
        public event OnClientDisconnectedEventHandler OnClientDisconnected;

        //Метод, подзволяющий упростить вызов события "Отключение клиента"
        /// <summary>
        /// Метод вызова события "при отключении клиента"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        /// <param name="Client">Отключившийся клиент</param>
        protected virtual void InvokeClientDisconnectedEvent(CTCPClient Client)
        {
            if(OnClientDisconnected != null)
                OnClientDisconnected.BeginInvoke(this, new OnClientDisconnectedEventArgs(Client),
                    null, null);
        }

        //Событие "получены данные от пользователя"
        /// <summary>
        /// Событие, возникающие при получении данных подключённым клиентом
        /// </summary>
        public event OnReceivedDataEventHandler OnReceiveData;

        //Метод, подзволяющий упростить вызов события "Получение данных от клиента"
        /// <summary>
        /// Метод вызова события "при получении данных"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        /// <param name="Client">Клиент, получивший данные</param>
        /// <param name="Data">Полученные данные (надо переписать, поменяв на полученный от клиента аргумент соответствующего события)</param>
        protected virtual void InvokeReceiveDataEvent(CTCPClient Client, DataEventArgs ClientArgs)
        {
            if(OnReceiveData != null)
                OnReceiveData.BeginInvoke(this, new ServerEvents.Args.DataEventArgs(Client, ClientArgs), null,
                    null);
        }

        /// <summary>
        /// Событие, возникающие при отправке данных подключённым клиентом
        /// </summary>
        public event OnDataSendEventHandle OnDataSend;

        /// <summary>
        /// Метод вызова события "при передаче данных"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        /// <param name="Client">Клиент, инициировавший передачу</param>
        /// <param name="ClientArgs">Параметры передачи</param>
        protected virtual void InvokeDataSendEvent(CTCPClient Client, DataEventArgs ClientArgs)
        {
            if(OnDataSend != null)
                OnDataSend.BeginInvoke(this, new ServerEvents.Args.DataEventArgs(Client, ClientArgs), null, null);
        }


        //Событие "возникла ошибка"
        /// <summary>
        /// Событие, возникающие при возникновении ошибки
        /// </summary>
        public event OnErrorEventHandler OnError;

        //Метод, подзволяющий упростить вызов события "Возникновение ошибки"
        /// <summary>
        /// Метод вызова события "при ошибке"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        /// <param name="Error">Возникшая ошибка</param>
        protected virtual void InvokeErrorEvent(Exception Error)
        {
            if(OnError != null)
                OnError.BeginInvoke(this, new OnErrorEventArgs(Error), null, null);
            else
                throw Error;
        }

        #endregion

        /* - Атрибуты класса ----------------------------------------------------------------------------------------------------------------- */

        #region Атрибуты

        /// <summary>
        /// Поле, содержащее текущий прослушиваемый порт
        /// </summary>
        protected int fPort = 8081;

        protected Encoding fDataEncoding = Encoding.ASCII;

        /// <summary>
        /// Тип прослушиваемых IP адресов
        /// </summary>
        protected IPAddress fAddressType = IPAddress.Any;

        /// <summary>
        /// Основной элемент сервера, производящий прослушивание порта и подключения входящих клиентов
        /// </summary>
        protected TcpListener fListner;
        /// <summary>
        /// Главный поток сервера
        /// </summary>
        protected Thread fServerThread;
        /// <summary>
        /// Поле, содержащее информацию о активности сервера
        /// </summary>
        protected bool fEnabel;
        /// <summary>
        /// Mutex синхронизации процесса асинхронного подключения клиентов
        /// </summary>
        protected ManualResetEvent fClientConnection;

        /// <summary>
        /// Список подключённых клиентов
        /// </summary>
        protected List<CTCPClient> fClientList;

        #endregion

        /* - СВойства класса ----------------------------------------------------------------------------------------------------------------- */

        #region Свойства

        /// <summary>
        /// Свойство отражает состояние сервера
        /// </summary>
        /// <value>подключён / отключён</value>
        public bool Enabeld
        {
            get { return fEnabel; }
            set { if(value) Start(); else Stop(); }
        }

        /// <summary>
        /// Свойство, указывающее прослушиваемый порт
        /// </summary>
        /// <value>Должно быть целым в пределах от 1 до 2^16</value>
        public int Port
        {
            get { return fPort; }
            set
            {
                if(value == fPort) return;

                if(value < 1 || value > 65535)
                    throw new ArgumentOutOfRangeException("Port", value, "Порт должен быть в пределах от 1 до 65535, а указан " +
                        value.ToString());

                bool Temp = Enabeld;
                Enabeld = false;

                fPort = value;

                Enabeld = Temp;
            }
        }

        #endregion

        /* - Время жизни объекта ------------------------------------------------------------------------------------------------------------- */

        #region Конструктор / диструктор

        //Конструктор с параметром "Порт"
        /// <summary>
        /// Конструктор с указанием прослушиваемого порта
        /// </summary>
        /// <param name="Port">Прослушиваемй порт</param>
        public CTCPServer(int Port)
        {
            if(Port < 1 || Port > 65535)
                throw new ArgumentOutOfRangeException(nameof(Port), Port, "Порт должен быть в пределах от 1 до 65535, а указан " + Port.ToString());
            fPort = Port;
        }

        //Конструктор с параметрами "Порт" и "класс обслуживаемых сетей"
        /// <summary>
        /// Конструктор с указанием прослушиваемого порта и типа обслуживаемых подсетей
        /// </summary>
        /// <param name="Port">Прослушиваемый порт</param>
        /// <param name="AddressType">Система IP адресов</param>
        public CTCPServer(int Port, IPAddress AddressType)
        {
            if(Port < 1 || Port > 65535)
                throw new ArgumentOutOfRangeException(nameof(Port), Port, "Порт должен быть в пределах от 1 до 65535, а указан " + Port.ToString());
            fPort = Port;
            fAddressType = AddressType;
        }

        #endregion

        /* - Запуск / остановка сервера ----------------------------------------------------------------------------------------------------- */

        #region Запуск / остановка

        //Метод запуска сервера
        /// <summary>
        /// Метод запуска сервера
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        protected void Start()
        {
            //Если сервер активен, то дальнейшие действия бессмыслены, следовательно, выходим из блока.
            if(Enabeld) return;

            try
            {
                //Устанавливаем признак активности сервера
                fEnabel = true;

                //Создаём новый экземпляр "слушателя"
                fListner = new TcpListener(fAddressType, fPort);
                fListner.Start();
                //Создаём блокирующий объект для синхронизации потоков
                fClientConnection = new ManualResetEvent(false);
                //Создаём список обслуживаемых клиентов
                fClientList = new List<CTCPClient>(10);

                //Создаём основной поток обработки подключений
                fServerThread = new Thread(new ThreadStart(Listen));
                //Запускаем сервер
                fServerThread.Start();

                InvokeStartEvent();
            }
            catch(SocketException SocketError)
            {
                Stop();
                InvokeErrorEvent(SocketError);
            }
        }

        //Метод остановки сервера
        /// <summary>
        /// Метод остановки сервера
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        protected void Stop()
        {
            //Если сервер неактивен, то дальнейшие действия бессмыслены
            if(!Enabeld) return;

            //Устанавливаем признак активности сервера в состояние "отключён"
            fEnabel = false;

            if(fClientList != null)
                for(int i = 0; i < fClientList.Count; i++) fClientList[i].Connected = false;

            //Останавливаем слушателя
            fListner.Stop();
            if(fClientConnection != null)
                fClientConnection.Set();

            //Если поток всё ещё активен, то...
            if(fServerThread != null)
                if(fServerThread.IsAlive)
                {
                    //Вызываем остановку потока вручную.
                    fServerThread.Abort();
                    //Ожидаем завершения потока (синхронизация)
                    fServerThread.Join();
                }

            //Обнуляем ссылки
            {
                fServerThread = null;
                fListner = null;
                fClientConnection = null;
            }

            InvokeStopEvent();
        }

        #endregion

        /* - Обработка подключений к серверу ------------------------------------------------------------------------------------------------- */

        #region Обработка подключений

        //Асинхронный метод обработки подключений к серверу. Работает в отдельном потоке.
        /// <summary>
        /// Главный метод сервера.
        /// </summary>
        /// <remarks>
        /// В нём производиться асинхронная обработка подключений клиентов
        /// Стартует в отдельном потоке
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        protected void Listen()
        {
            //Основной цикл сервера. Предназначен для определения факта подключения
            while(Enabeld) //Признак продолжения работы - активность сервера
            {
                try //Блок обработки ошибок
                {
                    fClientConnection.Reset();
                    //Асинхронный захват подключения с передачей дальнейшей обработки в метод void ClientAccepted(IAsyncResult AsyncResult)
                    fListner.BeginAcceptTcpClient(new AsyncCallback(ClientAccepted), fListner);
                    //Ожидаем флага разрешения дальнейшей работы
                    fClientConnection.WaitOne();
                }
                catch(SocketException SocketError) //Перехват ошибки соккета (зачем нужен, пока непонятно...)
                {
                    //Генерация события ошибки с передачей самой ошибки, как параметра
                    InvokeErrorEvent(SocketError);
                }
                catch(Exception Error) //Перехват остальных ошибок
                {
                    InvokeErrorEvent(Error);
                }
            }
        }

        //Завершение асинхронного процесса подключений клиента к серверу
        /// <summary>
        /// Метод асинхронного завершения подлючения клиента
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        protected void ClientAccepted(IAsyncResult AsyncResult)
        {
            TcpListener Listner = (TcpListener)AsyncResult.AsyncState;

            if(!Enabeld) return;

            //Обработка исключительных ситуаций (какая умная фраза!)
            try
            {
                //Получаем подключившегося клиента
                TcpClient Client = Listner.EndAcceptTcpClient(AsyncResult);

                if(Client == null) return;

                IncapsulateClient(Client);

            }
            catch(Exception Error) //Перехват ошибки
            {
                //Генерация события "ошибка"
                InvokeErrorEvent(Error);
            }
            //Разрешение на дальнейшую обработку подключений клиента
            fClientConnection.Set();
        }

        protected virtual void IncapsulateClient(TcpClient Client)
        {
            //Создаём новый экземпляр класса "CTCPClient", в котором будет происходить дальнейшая работа с клиентом
            //CTCPClient TCPClient = new CTCPClient(Client);
            CTCPClient TCPClient = (CTCPClient)Client;

            //подписываемся на обработчики событий клиента
            TCPClient.OnConnected += new OnConnectedEventHandler(TCPClient_OnConnected);
            TCPClient.OnDisconnected += new OnDisconnectedEventHandler(TCPClient_OnDisconnected);
            TCPClient.OnReceiveData += new OnReceiveDataEventHandler(TCPClient_OnReceiveData);
            TCPClient.OnError += new ClientEvents.Handlers.OnErrorEventHandler(TCPClient_OnError);
            TCPClient.OnDataSend += new OnDataSendEventHandler(TCPClient_OnDataSend);

            TCPClient.DataEncoding = fDataEncoding;

            //Добавляем клиента в список
            fClientList.Add(TCPClient);
            InvokeClientConnectedEvent(TCPClient);
        }

        #endregion

        /* - Обработка событий подключившихся клиентов --------------------------------------------------------------------------------------- */

        #region Обработка событий подключённых клиентов

        /// <summary>
        /// Метод обработки событий подключённых клиентов "при отправке данных"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        /// <param name="Client">Клиент, отправивший данные</param>
        /// <param name="Args">Параметры</param>
        void TCPClient_OnDataSend(CTCPClient Client, DataEventArgs Args)
        {
            InvokeDataSendEvent(Client, Args);
        }

        //Обработчик события ошибки клиента
        /// <summary>
        /// Метод обработки событий подключённых клиентов "при ошибке"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        /// <param name="Client">Клиент, собвершивший ошибку</param>
        /// <param name="Args">Параметры</param>
        void TCPClient_OnError(CTCPClient Client, OnErrorEventArgs Args)
        {
            //Передаётся, как ошибка сервера (Не совсем корректно. Надо передавать так же в виде параметра самого виновника ошибки)
            InvokeErrorEvent(Args.Error);
        }

        //обработчик события получения данных от клиента 
        /// <summary>
        /// Метод обработки событий подключённых клиентов "при получении данных"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        /// <param name="Client">Клиент, получивший данные</param>
        /// <param name="Args">Параметры</param>
        void TCPClient_OnReceiveData(CTCPClient Client, DataEventArgs Args)
        {
            //Передаётся клиент, от которого пришли данные и сами данные в виде масива строк
            InvokeReceiveDataEvent(Client, Args);
        }

        //обработчик события отключения клиента
        /// <summary>
        /// Метод обработки событий подключённых клиентов "при отключении"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        /// <param name="Client">Отключившийся клиент</param>
        /// <param name="Args">Параметры</param>
        void TCPClient_OnDisconnected(CTCPClient Client, EventArgs Args)
        {
            //Извлекает отключившегося клиента из списка подключившихся клиентов
            fClientList.Remove(Client);

            //Генерирует событие "Отключение клиента" с указанием отключившегося.
            InvokeClientDisconnectedEvent(Client);
        }

        //Обработчик события подключения клиента
        /// <summary>
        /// Метод обработки событий подключённых клиентов "при подключении"
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// (Зачем нужен - непонятно... нигде ниразу вызван не был. Но пусть будет пока...)
        /// </remarks>
        /// <param name="Client">Подключившийся клиент</param>
        /// <param name="Args">Параметры</param>
        void TCPClient_OnConnected(CTCPClient Client, EventArgs Args)
        {
            //Генерирует событие подключение клиента к серверу с указанием слиента
            InvokeClientConnectedEvent(Client);
        }

        #endregion

        /* ----------------------------------------------------------------------------------------------------------------------------------- */

        public override string ToString() { return "TCP:" + Port.ToString(); }

        #region IEnumerable<CTCPClient> Members

        /// <summary>
        /// Свойство отражает число подключённых к серверу клиентов
        /// </summary>
        /// <value>целое</value>
        public int ClientCount { get { return fClientList.Count; } }

        public CTCPClient this[int Index] { get { return fClientList[Index]; } }

        /// <summary>
        /// Метод получения индексатора
        /// </summary>
        public IEnumerator<CTCPClient> GetEnumerator()
        {
            return new ClientEnumerator(this);
        }

        /// <summary>
        /// Метод получения индексатора реализации интерфейса IEnumirable
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        /// <returns>Индексатор</returns>
        IEnumerator<CTCPClient> IEnumerable<CTCPClient>.GetEnumerator() { return GetEnumerator(); }

        /// <summary>
        /// Метод, реализующий интерфейс IEnumirable.
        /// </summary>
        /// <remarks>
        /// Метод для технического использования.
        /// В интерфейс класса не входит.
        /// </remarks>
        /// <returns>Индексатор</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Stop();
        }

        void IDisposable.Dispose()
        {
            Dispose();
        }

        #endregion

        #region IServer Members

        int IServer.ClientCount
        {
            get { return ClientCount; }
        }
        int IServer.Port
        {
            get { return Port; }
            set { Port = value; }
        }
        bool IServer.Enabeld
        {
            get { return Enabeld; }
            set { Enabeld = value; }
        }

        CTCPClient IServer.this[int Index] { get { return this[Index]; } }

        event OnClientConnectedEventHandler IServer.OnClientConnected
        {
            add { OnClientConnected += value; }
            remove { OnClientConnected -= value; }
        }
        event OnClientDisconnectedEventHandler IServer.OnClientDisconnected
        {
            add { OnClientDisconnected += value; }
            remove { OnClientDisconnected -= value; }
        }
        event OnDataSendEventHandle IServer.OnDataSend
        {
            add { OnDataSend += value; }
            remove { OnDataSend -= value; }
        }
        event OnErrorEventHandler IServer.OnError
        {
            add { OnError += value; }
            remove { OnError -= value; }
        }
        event OnReceivedDataEventHandler IServer.OnReceiveData
        {
            add { OnReceiveData += value; }
            remove { OnReceiveData -= value; }
        }
        event OnStartEventHandler IServer.OnStart
        {
            add { OnStart += value; }
            remove { OnStart -= value; }
        }
        event OnStopEventHandler IServer.OnStop
        {
            add { OnStop += value; }
            remove { OnStop -= value; }
        }

        #endregion
    }
}
